﻿#Requires AutoHotkey v2.0
#SingleInstance Force

CoordMode 'Mouse', 'Screen'
CoordMode 'ToolTip', 'Screen'

TraySetIcon './ScreenPaste.ico'

configFilePath := A_ScriptDir '\config.txt'
cfs := CustomFs(configFilePath)
cfs.Init()
config := Configuration(cfs)
config.Custom()
Init()

#Include './util/CustomFs.ahk'
#Include './util/Tip.ahk'
#Include './util/SetCursor.ahk'
#Include './util/CreatDIBSection.ahk'
#Include './util/BMP_OP.ahk'
#Include './util/Common.ahk'
#Include './ui/MyInputBox.ahk'
#Include './ui/MyToolBar.ahk'
#Include './ui/Helper.ahk'
#Include './ui/Setting.ahk'

class Configuration {
  __New(cfs) {
    this.noBgc := cfs.Get('noBgc')
    this.guiBgc := cfs.Get('guiBgc')
    this.useRandomBgc := cfs.Get('useRandomColor')
    this.guiTransparent := cfs.Get('guiTRP')
    this.showMask := cfs.Get('showMask')
    this.maskBgc := cfs.Get('maskBgc')
    this.maskInitTransparent := cfs.Get('maskTRP')
    this.pasteInstantly := cfs.Get('pasteIST')
    this.singleton := cfs.Get('singleton')
    this.withTip := cfs.Get('tip')
    this.clickCount := cfs.Get('trayClick')
    this.clipHK := cfs.Get('clipHK')
    this.cancelHK := cfs.Get('cancelHK')
    this.clearAllHK := cfs.Get('clearAllHK')
    this.historyPath := cfs.Get('hisPath')
    this.defaultSavePath := cfs.Get('defaultSave')
    this.autoSave := cfs.Get('autoSave')
    this.guiOption := '+AlwaysOnTop -Caption +ToolWindow +Border +OwnDialogs'
  }

  Custom() {
    if !this.pasteInstantly && this.noBgc
      this.showMask := true
  }
}

isReady := true

Init() {
  groupPath := cfs.Get('groupPath')
  hisPath := cfs.Get('hisPath')
  list := Array(groupPath, hisPath)
  for v in list
    if !DirExist(v)
      DirCreate(v)
  groupsPathList := cfs.Get('group')
  for v in groupsPathList
    if !DirExist(groupPath '\' v)
      DirCreate(groupPath '\' v)
}

tray := A_TrayMenu
tray.delete
tray.add '截图', (*) => Start()
tray.add '截全屏并复制', (*) => SaveToClipBoard(0)
tray.add '打开上一张截图', (*) => PasteHistory()
tray.add '打开位图文件', (*) => PasteBMPFile(FileSelect(, config.defaultSavePath, , '(*.bmp)'))
tray.add
tray.add '设置', (*) => Setting.Show()
tray.add '打开文件夹', (*) => Run(A_ScriptDir)
tray.add
tray.add '帮助', (*) => Helper.Show()
tray.add
tray.add '重新启动', (*) => Reload()
tray.add "退出", (*) => ExitApp()
tray.ClickCount := config.clickCount
tray.Default := '截图'

Hotkey config.clipHK, Start
Start(*) {
  global
  if !isReady
    return
  isReady := false
  ToolTip 'READY', 100, 50
  Hotkey 'LButton', Config.pasteInstantly ? StartClip : StartClipWithToolBar, 'On'
  Hotkey config.cancelHK, Cancel, 'On'
}

#Esc:: {
  res := MsgBox('Exit?', , 0x1)
  if res != 'OK'
    return
  ExitApp()
}

Hotkey config.clearAllHK, ClearAll
ClearAll(*) {
  Tip.ShowTip('Clear all')
  for w in WinGetList('ahk_pid ' ProcessExist())
    if GuiFromHwnd(w) is Gui
      WinClose('ahk_id' w)
}

Cancel(*) {
  Hotkey 'LButton', 'Off'
  Hotkey config.cancelHK, 'Off'
  global isReady := true
  Mask.Close()
  ToolTip
  Tip.ShowTip('CANCEL', , , 2)
}

StartClip(*) {
  Mask.Show()
  ToolTip
  g := GetGui(config.withTip)
  Mask.Close()
  Hotkey 'LButton', 'Off'
  Hotkey config.cancelHK, 'Off'
  global isReady := true
  ToolTip
  ToolTip , , , 2
  g.GetPos(&x, &y, &w, &h)
  g.OnEvent('ContextMenu', OnCM)
  proxy := g.AddText('xp w' w ' h' h)
  proxy.OnEvent('Click', (*) => MoveWin(g.Hwnd))
  if config.singleton
    proxy.OnEvent('DoubleClick', (*) => g.Hide())
  else
    proxy.OnEvent('DoubleClick', (*) => g.Destroy())
  Paste(g.Hwnd, IsTrue(config.autoSave, (*) => SaveToFile(g.Hwnd, config.historyPath, 'last.bmp'), (*) =>))
}

StartClipWithToolBar(*) {
  Mask.Show()
  ToolTip
  g := GetGui(config.withTip)
  Hotkey 'LButton', 'Off'
  g.GetPos(&x, &y, &w, &h)
  bar := MyToolBar(2, ['取消', '确定'], x + w - MyButton.btnW * 2, y + h
    , [(*) => Bar_CancelCB(bar, g), (*) => Bar_OkCB(bar, g)])
  bar.Show()
  ToolTip
  ToolTip , , , 2
  Hotkey '~LButton', (*) => ChangeSize(bar, g), 'On'
  Hotkey config.cancelHK, (*) => Cancel_(bar, g), 'On'
}

Cancel_(bar, g, *) {
  g.Destroy()
  bar.Destroy()
  Hotkey '~LButton', 'Off'
  Hotkey config.cancelHK, 'Off'
  global isReady := true
  Mask.Close()
  ToolTip
  Tip.ShowTip('CANCEL', , , 2)
}

ChangeSize(bar, g, *) {
  if !IsOverGui(g.Hwnd)
    return
  direction := OverWhere(g.Hwnd)
  switch direction {
    case 0: t := CursoType.sizeAll
    case 2, 19: t := CursoType.sizeNS
    case 9, 22: t := CursoType.sizeNESW
    case 26, 5: t := CursoType.sizeNWSE
    case 3, 7: t := CursoType.sizeWE
  }
  MoveWinAndToolBar(g, bar, t, direction)
}

IsOverGui(hwnd) {
  WinGetPos(&guiX, &guiY, &guiW, &guiH, 'ahk_id' hwnd)
  MouseGetPos(&x, &y)
  if x < guiX || x > guiX + guiW
    return false
  if y < guiY || y > guiY + guiH
    return false
  return true
}

OverWhere(hwnd) {
  WinGetPos(&guiX, &guiY, &guiW, &guiH, 'ahk_id' hwnd)
  MouseGetPos(&x, &y)
  bT := (guiW > 60 && guiH > 60) ? 25 : 10 ; borderThreshold
  r := 0
  if x < guiX + bT ; left
    r += 3
  else if x > guiX + guiW - bT ; right
    r += 7
  if y < guiY + bT ; up
    r += 2
  else if y > guiY + guiH - bT ; bottom
    r += 19
  return r
}

Bar_CancelCB(bar, g) {
  Hotkey '~LButton', 'Off'
  Hotkey config.cancelHK, 'Off'
  global isReady := true
  Mask.Close()
  ToolTip
  bar.Destroy()
  g.Destroy()
}

Bar_OkCB(bar, g) {
  Hotkey '~LButton', 'Off'
  Hotkey config.cancelHK, 'Off'
  global isReady := true
  Mask.Close()
  ToolTip
  bar.Destroy()
  g.GetPos(&x, &y, &w, &h)
  g.OnEvent('ContextMenu', OnCM)
  proxy := g.AddText('xp w' w ' h' h)
  proxy.OnEvent('Click', (*) => MoveWin(g.Hwnd))
  if config.singleton
    proxy.OnEvent('DoubleClick', (*) => g.Hide())
  else
    proxy.OnEvent('DoubleClick', (*) => g.Destroy())
  Paste(g.Hwnd, IsTrue(config.autoSave, (*) => SaveToFile(g.Hwnd, config.historyPath, 'last.bmp'), (*) =>))
}

OnCM(obj, ctrlObj, item, isR, x, y) {
  m := Menu()
  m.Add '关闭', (*) => obj.Destroy()
  m.Add '关闭全部', (*) => ClearAll()
  m.Add
  m.Add '存入剪贴板', (*) => SaveToClipBoard(obj.Hwnd)
  m.Add '图像另存为...', (*) => SaveToFile(obj.Hwnd)
  m.Add
  m.Add '关闭并存入剪贴板', (*) => SaveToClipBoard(obj.Hwnd, true)
  m.Add '关闭并保存到默认组', (*) => SaveToFile(obj.Hwnd, config.defaultSavePath, true)
  m.Add
  sm := Menu()
  Extend(sm, obj.Hwnd)
  sm.Add
  sm.Add '新建分组', (*) => CreateGroup(obj.Hwnd)
  m.Add '存入组', sm
  m.Show()
}

Extend(sm, pass) {
  menuList := cfs.Get('group')
  for v in menuList
    sm.Add v, (itemName, itemPos, myMenu) => SaveToGroup(itemName, pass)
  defaultMenu := cfs.Get('defaultSave')
  defaultMenuName := SubStr(defaultMenu, InStr(defaultMenu, '\', , -1) + 1)
  sm.Check(defaultMenuName)
}

CreateGroup(pass) {
  ib := MyInputBox('输入组名:', '新建组', pass)
  if ib.Result = 'Ok' {
    DirCreate(cfs.Get('groupPath') '\' ib.Value)
    groups := cfs.Get('group')
    groups.Push(ib.Value)
    cfs.Set('group', groups)
    cfs.Update()
    if ib.checked {
      path := cfs.Get('groupPath') '\' ib.value
      SaveToFile(pass, path)
      Tip.ShowTip('创建并保存了喵~')
    } else
      Tip.ShowTip('创建成功喵~')
  }
}

SaveToGroup(groupName, hwnd) {
  path := cfs.Get('groupPath') '\' groupName
  SaveToFile(hwnd, path)
}

MoveWin(hwnd, *) {
  MouseGetPos(&px, &py)
  WinGetPos(&wx, &wy, , , 'ahk_id' hwnd)
  dx := wx - px, dy := wy - py
  SetWinDelay -1
  While GetKeyState("LButton", "P") {
    MouseGetPos(&nx, &ny)
    WinMove(nx + dx, ny + dy, , , 'ahk_id' hwnd)
  }
}

MoveWinAndToolBar(frame, bar, cursor, dire, *) {
  ; Don't fall into the logic
  bar.Hide()
  ToolTip
  MouseGetPos(&px, &py)
  frame.GetPos(&wx, &wy, &ww, &wh)
  dx := wx - px, dy := wy - py
  SetWinDelay -1
  SetCursor(cursor)
  While GetKeyState("LButton", "P") {
    MouseGetPos(&nx, &ny)
    if dire = 0 {
      x := nx + dx <= 0 ? 0 : nx + dx
      x := ww + dx + nx >= A_ScreenWidth ? A_ScreenWidth - ww : x
      y := ny + dy <= 0 ? 0 : ny + dy
      y := wh + dy + ny >= A_ScreenHeight ? A_ScreenHeight - wh : y
      frame.Move(x, y)
    } else if dire = 3
      frame.Move(nx + dx, , ww + px - nx)
    else if dire = 7
      frame.Move(, , ww + nx - px)
    else if dire = 2
      frame.Move(, ny + dy, , wh + py - ny)
    else if dire = 19
      frame.Move(, , , wh + ny - py)
    else if dire = 5
      frame.Move(nx + dx, ny + dy, ww + px - nx, wh + py - ny)
    else if dire = 26
      frame.Move(, , ww + nx - px, wh + ny - py)
    else if dire = 9
      frame.Move(, ny + dy, ww + nx - px, wh + py - ny)
    else if dire = 22
      frame.Move(nx + dx, , ww + px - nx, wh + ny - py)
  }
  SetCursor(CursoType.arrow)
  bar.Show()
  frame.GetPos(&wx, &wy, &ww, &wh)
  y := wy + wh >= A_ScreenHeight ? A_ScreenHeight - wh - MyToolBar.btnH : wy + wh
  bar.Move(wx + ww - MyToolBar.btnW * bar.btnCount, y)
  if config.withTip
    ToolTip ww 'X' wh, wx, wy + wh
}

class Mask extends Gui {
  static insId := ''
  __New() {
    super.__New('+AlwaysOnTop -Caption +ToolWindow')
    this.BackColor := Config.maskBgc
  }
  static Show(*) {
    if !Config.showMask
      return
    ins := Mask()
    ins.Show('Minimize x0 y0 w' A_ScreenWidth ' h' A_ScreenHeight)
    WinSetTransparent(0, 'ahk_id' ins.Hwnd)
    ins.Restore()
    WinSetTransparent(Config.maskInitTransparent, 'ahk_id' ins.Hwnd)
    this.insId := ins.Hwnd
  }
  static Close() {
    try
      WinClose('ahk_id' this.insId)
  }
}

GetGui(withTip := true) {
  g := Config.singleton
    ? GetAGui()
    : Gui(Config.guiOption)
  g.BackColor := Config.useRandomBgc
    ? GetRandomColor()
    : Config.guiBgc
  MouseGetPos &begin_x, &begin_y
  g.Show('x' begin_x ' y' begin_y)
  if Config.noBgc
    WinSetTransColor(Config.guiBgc, 'ahk_id' g.Hwnd)
  else
    WinSetTransparent(Config.guiTransparent, 'ahk_id' g.Hwnd)
  last_x := 0, last_y := 0
  while GetKeyState('LButton', 'P') {
    MouseGetPos &end_x, &end_y
    x := End_x < Begin_x ? End_x : Begin_x
    y := End_y < Begin_y ? End_y : Begin_y
    ; caculate gui size
    scope_x := Abs(begin_x - end_x)
    scope_y := Abs(begin_y - end_y)
    if withTip && (last_x != end_x || last_y != end_y) {
      ToolTip(scope_x 'X' scope_y, x, y - 20, 1)
      ToolTip('(' end_x ',' end_y ')', end_x, end_y + 20, 2)
    }
    last_x := end_x
    last_y := end_y
    g.Move(x, y, scope_x, scope_y)
  }
  return g
}

GetAGui() {
  static g := Gui(Config.guiOption)
  return g
}

GetRandomColor() {
  rx := Random(0, A_ScreenWidth)
  ry := Random(0, A_ScreenHeight)
  return Config.guiBgc := PixelGetColor(rx, ry, 'RGB')
}

Paste(hwnd, CallBack := unset) {
  hdd_frame := DllCall("GetDC", 'UInt', 0)
  hdc_frame := DllCall("GetDC", 'UInt', hwnd)
  WinSetTransColor(Config.guiBgc, 'ahk_id' hwnd)
  WinGetPos(&x, &y, &Rx, &Ry, 'ahk_id' hwnd)
  DllCall("gdi32.dll\StretchBlt", 'UInt', hdc_frame, 'Int'
    , 0, 'Int', 0, 'Int', Rx, 'Int', Ry
    , 'UInt', hdd_frame, 'UInt', x + 1, 'UInt', y + 1 ; remove border
    , 'Int', Rx, 'Int', Ry, 'UInt', 0xCC0020) ; SRCCOPY
  ; Tip.ShowTip('OVER')
  DllCall('user32.dll\ReleaseDC', 'int', 0, 'uint', hdc_frame)
  DllCall('user32.dll\ReleaseDC', 'int', 0, 'uint', hdd_frame)
  if IsSet(CallBack)
    CallBack()
}

PasteHistory(*) {
  path := config.historyPath '/last.bmp'
  if !FileExist(path) {
    MsgBox '没有历史截图喵~'
    return
  }
  PasteBMPFile(path)
}

PasteBMPFile(path) {
  ReadBMPWH(path, &w, &h)
  g := Gui('+AlwaysOnTop -Caption +ToolWindow')
  g.Show('x100 y100 w' w ' h' h)
  g.OnEvent('ContextMenu', OnCM)
  proxy := g.AddText('xp w' w ' h' h)
  proxy.OnEvent('Click', (*) => MoveWin(g.Hwnd))
  proxy.OnEvent('DoubleClick', (*) => g.Destroy())
  DisplayBitmap(path, g.Hwnd, w, h)
  WinSetTransparent(255, 'ahk_id' g.Hwnd)
}

SaveToClipBoard(hwnd, closePaste := false) {
  hwnd ? WinGetPos(, , &w, &h, 'ahk_id' hwnd) : w := A_ScreenWidth, h := A_ScreenHeight
  BMPFromHwnd(&hBitmap, hwnd, w, h)
  if closePaste
    WinClose('ahk_id' hwnd)
  DllCall("OpenClipboard", "Ptr", 0)
  DllCall("EmptyClipboard")
  DllCall("SetClipboardData", "uint", 2, "Ptr", hBitmap)
  DllCall("CloseClipboard")
  DllCall("DeleteObject", "Ptr", hBitmap)
  Tip.ShowTip('已存入喵~')
}

SaveToFile(hwnd, path := unset, fileName := unset, closePaste := false) {
  WinSetAlwaysOnTop(0, 'ahk_id' hwnd)
  IsSet(fileName) || fileName := SubStr(A_ScriptName, 1, StrLen(A_ScriptName) - 4) '_' FormatTime(, "yyyy-MM-dd_hh-mm-ss") '.bmp'
  fullPath := IsSet(path)
    ? path '\' fileName
    : FileSelect("S", config.defaultSavePath '\' fileName, '图像另存为', '(*.bmp)')
  WinSetAlwaysOnTop(1, 'ahk_id' hwnd)
  if !fullPath
    return
  WinGetPos(, , &w, &h, 'ahk_id' hwnd)
  BMPFromHwnd(&hBM, hwnd, w, h)
  if closePaste
    WinClose('ahk_id' hwnd)
  tmp := CreateDIBSection(w, h)
  GetBitmapWH(hBM, &bw, &bh)
  CopyHBM(tmp, hBM, bw, bh, 0, 0, 0, 0)
  DllCall("DeleteObject", "Ptr", hBM)
  hBM := tmp
  struct := Buffer(sos := (A_PtrSize = 8 ? 104 : 84))
  DllCall("GetObject", "Ptr", hBM, "int", sos, "Ptr", struct)
  bfh := struct.Ptr + (A_PtrSize = 8 ? 32 : 24)
  size := NumGet(bfh + 20, 'uint')
  pBits := NumGet(bfh - A_PtrSize, 'ptr')
  bf := Buffer(14, 0)
  NumPut('short', 0x4D42, bf)
  NumPut('uint', size + 54, bf, 2)
  NumPut('uint', 54, bf, 10)
  try {
    f := FileOpen(fullPath, "w")
    f.RawWrite(bf, 14)
    f.RawWrite(bfh, 40)
    f.RawWrite(pBits, size)
    f.Close()
    Tip.ShowTip('成功保存到文件喵~')
  } catch as e {
    MsgBox e.Message
    Tip.ShowTip('失败了...')
  } finally {
    DllCall("DeleteObject", "Ptr", hBM)
  }
}